package com.riche.codefactory.service.impl;

import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.riche.codefactory.common.ErrorCode;
import com.riche.codefactory.constant.CommonConstant;
import com.riche.codefactory.exception.BusinessException;
import com.riche.codefactory.exception.ThrowUtils;
import com.riche.codefactory.mapper.QuestionBankQuestionMapper;
import com.riche.codefactory.model.c2s.questionBankQuestion.C2sQuestionBankQuestionQueryRequest;
import com.riche.codefactory.model.domain.Question;
import com.riche.codefactory.model.domain.QuestionBank;
import com.riche.codefactory.model.domain.QuestionBankQuestion;
import com.riche.codefactory.model.domain.User;
import com.riche.codefactory.model.s2c.S2cQuestionBankQuestion;
import com.riche.codefactory.model.s2c.S2cUser;
import com.riche.codefactory.service.IQuestionBankQuestionSVC;
import com.riche.codefactory.service.IQuestionBankSVC;
import com.riche.codefactory.service.IQuestionSVC;
import com.riche.codefactory.service.IUserSVC;
import com.riche.codefactory.utils.SqlUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.aop.framework.AopContext;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 题库题目关联服务实现
 *
 */
@Service
@Slf4j
public class QuestionBankQuestionSVC extends ServiceImpl<QuestionBankQuestionMapper, QuestionBankQuestion> implements IQuestionBankQuestionSVC {

    @Resource
    private IUserSVC userService;

    @Resource
    private IQuestionSVC IQuestionSVC;

    @Resource
    private IQuestionBankSVC IQuestionBankSVC;

    /**
     * 校验数据
     *
     * @param questionBankQuestion
     * @param add                  对创建的数据进行校验
     */
    @Override
    public void validQuestionBankQuestion(QuestionBankQuestion questionBankQuestion, boolean add) {
        ThrowUtils.throwIf(questionBankQuestion == null, ErrorCode.PARAMS_ERROR);
        // 题目和题库必须存在才可以添加
        Long questionId = questionBankQuestion.getQuestionId();
        if (questionId != null) {
            ThrowUtils.throwIf(IQuestionSVC.getById(questionId) == null, ErrorCode.NOT_FOUND_ERROR, "题目不存在");
        }
        Long questionBankId = questionBankQuestion.getQuestionBankId();
        if (questionBankId != null) {
            ThrowUtils.throwIf(IQuestionBankSVC.getById(questionBankId) == null, ErrorCode.NOT_FOUND_ERROR, "题库不存在");
        }
        // todo 从对象中取值
//        String title = questionBankQuestion.getTitle();
//        // 创建数据时，参数不能为空
//        if (add) {
//            // todo 补充校验规则
//            ThrowUtils.throwIf(StringUtils.isBlank(title), ErrorCode.PARAMS_ERROR);
//        }
//        // 修改数据时，有参数则校验
//        // todo 补充校验规则
//        if (StringUtils.isNotBlank(title)) {
//            ThrowUtils.throwIf(title.length() > 80, ErrorCode.PARAMS_ERROR, "标题过长");
//        }
    }

    /**
     * 获取查询条件
     *
     * @param c2sQuestionBankQuestionQueryRequest
     * @return
     */
    @Override
    public QueryWrapper<QuestionBankQuestion> getQueryWrapper(C2sQuestionBankQuestionQueryRequest c2sQuestionBankQuestionQueryRequest) {
        QueryWrapper<QuestionBankQuestion> queryWrapper = new QueryWrapper<>();
        if (c2sQuestionBankQuestionQueryRequest == null) {
            return queryWrapper;
        }
        // todo 从对象中取值
        Long id = c2sQuestionBankQuestionQueryRequest.getId();
        Long notId = c2sQuestionBankQuestionQueryRequest.getNotId();
        String sortField = c2sQuestionBankQuestionQueryRequest.getSortField();
        String sortOrder = c2sQuestionBankQuestionQueryRequest.getSortOrder();
        Long userId = c2sQuestionBankQuestionQueryRequest.getUserId();
        Long questionBankId = c2sQuestionBankQuestionQueryRequest.getQuestionBankId();
        Long questionId = c2sQuestionBankQuestionQueryRequest.getQuestionId();
        // todo 补充需要的查询条件
        // 模糊查询
        // 精确查询
        queryWrapper.ne(ObjectUtils.isNotEmpty(notId), "id", notId);
        queryWrapper.eq(ObjectUtils.isNotEmpty(id), "id", id);
        queryWrapper.eq(ObjectUtils.isNotEmpty(userId), "userId", userId);
        queryWrapper.eq(ObjectUtils.isNotEmpty(questionBankId), "questionBankId", questionBankId);
        queryWrapper.eq(ObjectUtils.isNotEmpty(questionId), "questionId", questionId);
        // 排序规则
        queryWrapper.orderBy(SqlUtils.validSortField(sortField),
                sortOrder.equals(CommonConstant.SORT_ORDER_ASC),
                sortField);
        return queryWrapper;
    }

    /**
     * 获取题库题目关联封装
     *
     * @param questionBankQuestion
     * @param request
     * @return
     */
    @Override
    public S2cQuestionBankQuestion getQuestionBankQuestionVO(QuestionBankQuestion questionBankQuestion, HttpServletRequest request) {
        // 对象转封装类
        S2cQuestionBankQuestion s2cQuestionBankQuestion = S2cQuestionBankQuestion.objToVo(questionBankQuestion);

        // todo 可以根据需要为封装对象补充值，不需要的内容可以删除
        // region 可选
        // 1. 关联查询用户信息
        Long userId = questionBankQuestion.getUserId();
        User user = null;
        if (userId != null && userId > 0) {
            user = userService.getById(userId);
        }
        S2cUser s2cUser = userService.getUserVO(user);
        s2cQuestionBankQuestion.setUser(s2cUser);
        // endregion

        return s2cQuestionBankQuestion;
    }

    /**
     * 分页获取题库题目关联封装
     *
     * @param questionBankQuestionPage
     * @param request
     * @return
     */
    @Override
    public Page<S2cQuestionBankQuestion> getQuestionBankQuestionVOPage(Page<QuestionBankQuestion> questionBankQuestionPage, HttpServletRequest request) {
        List<QuestionBankQuestion> questionBankQuestionList = questionBankQuestionPage.getRecords();
        Page<S2cQuestionBankQuestion> questionBankQuestionVOPage = new Page<>(questionBankQuestionPage.getCurrent(), questionBankQuestionPage.getSize(), questionBankQuestionPage.getTotal());
        if (CollUtil.isEmpty(questionBankQuestionList)) {
            return questionBankQuestionVOPage;
        }
        // 对象列表 => 封装对象列表
        List<S2cQuestionBankQuestion> s2cQuestionBankQuestionList = questionBankQuestionList.stream().map(questionBankQuestion -> {
            return S2cQuestionBankQuestion.objToVo(questionBankQuestion);
        }).collect(Collectors.toList());

        // todo 可以根据需要为封装对象补充值，不需要的内容可以删除
        // region 可选
        // 1. 关联查询用户信息
        Set<Long> userIdSet = questionBankQuestionList.stream().map(QuestionBankQuestion::getUserId).collect(Collectors.toSet());
        Map<Long, List<User>> userIdUserListMap = userService.listByIds(userIdSet).stream()
                .collect(Collectors.groupingBy(User::getId));
        // endregion

        questionBankQuestionVOPage.setRecords(s2cQuestionBankQuestionList);
        return questionBankQuestionVOPage;
    }

    /**
     * 批量添加题目到题库
     *
     * @param questionIdList
     * @param questionBankId
     * @param loginUser
     */
    @Override
    public void batchAddQuestionToBank(List<Long> questionIdList, long questionBankId, User loginUser) {
        // 参数校验
        ThrowUtils.throwIf(CollUtil.isEmpty(questionIdList), ErrorCode.PARAMS_ERROR, "题目列表不能为空");
        ThrowUtils.throwIf(questionBankId <= 0, ErrorCode.PARAMS_ERROR, "题目 id 非法");
        ThrowUtils.throwIf(loginUser == null, ErrorCode.NOT_LOGIN_ERROR, "未登录或无权限");
        // 检查题目id是否存在（可以一堆题目id中有错误的id）
        LambdaQueryWrapper<Question> qustionLambdaQUeryWrapper = Wrappers.lambdaQuery(Question.class)
                .select(Question::getId)
                .in(Question::getId, questionIdList);
        // 合法的题目id列表
        List<Long> validQuestionIdList = IQuestionSVC.listObjs(qustionLambdaQUeryWrapper, obj -> (Long) obj);

        ThrowUtils.throwIf(CollUtil.isEmpty(validQuestionIdList), ErrorCode.NOT_FOUND_ERROR, "合法的题目Id列表为空");
        // todo 优化 检查哪些题目已存在于题库中，避免重复插入  提高健壮性
        LambdaQueryWrapper<QuestionBankQuestion> lambdaQueryWrapper = Wrappers.lambdaQuery(QuestionBankQuestion.class)
                .eq(QuestionBankQuestion::getQuestionBankId, questionBankId)
                .in(QuestionBankQuestion::getQuestionId, validQuestionIdList);
        List<QuestionBankQuestion> existQuestionList = this.list(lambdaQueryWrapper);
        Set<Long> existQuestionIdSet = existQuestionList.stream().map(QuestionBankQuestion::getId).collect(Collectors.toSet());
        // 已经存在于题库的的id不需要再次添加
        validQuestionIdList = validQuestionIdList.stream().filter(questionId -> {
            return !existQuestionIdSet.contains(questionId);
        }).collect(Collectors.toList());
        ThrowUtils.throwIf(CollUtil.isEmpty(validQuestionIdList), ErrorCode.NOT_FOUND_ERROR, "所有题目都已存在于题库中");
        // 判断题库id是否存在
        QuestionBank questionBank = IQuestionBankSVC.getById(questionBankId);
        ThrowUtils.throwIf(questionBank == null, ErrorCode.NOT_FOUND_ERROR, "题库不存在");
        // todo 自定义线程池(IO密集型线程池)
        ThreadPoolExecutor customExecutor = new ThreadPoolExecutor(
                4,                              // 核心线程数
                10,                                        // 最大线程数
                60L,                                       // 线程空闲存活时间
                TimeUnit.SECONDS,                          // 存活时间单位
                new LinkedBlockingQueue<>(1000),  // 阻塞队列容量
                new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略：由调用线程处理任务
        );
        // 保存所有的批次任务
        List<CompletableFuture<Void>> futures = new ArrayList<>();
        // 执行插入
        // todo 分批处理 避免长事务 假设每次处理 1000 条数据
        int batchSize = 1000;
        int totalQuestionListSize = validQuestionIdList.size();
        for (int i = 0; i < totalQuestionListSize; i += batchSize) {
            // 生成每批次的数据
            List<Long> subList = validQuestionIdList.subList(i, Math.min(i + batchSize, totalQuestionListSize));
            List<QuestionBankQuestion> questionBankQuestions = subList.stream().map(questionId -> {
                QuestionBankQuestion questionBankQuestion = new QuestionBankQuestion();
                questionBankQuestion.setQuestionBankId(questionBankId);
                questionBankQuestion.setQuestionId(questionId);
                questionBankQuestion.setUserId(loginUser.getId());
                return questionBankQuestion;
            }).collect(Collectors.toList());
            // 使用事务处理没批数据
            // 获取代理方法
            IQuestionBankQuestionSVC IQuestionBankQuestionSVC = (QuestionBankQuestionSVC) AopContext.currentProxy();

            // todo 异步处理每批数据,将任务添加到异步任务列表
            CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
                IQuestionBankQuestionSVC.batchAddQuestionToBankInner(questionBankQuestions);
            }, customExecutor);
            futures.add(future);
        }
        // 等待所有的批次任务执行完成
        CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();

        // 全部执行完成后，关闭线程池
        customExecutor.shutdown();
    }

    /**
     * 批量添加题目到题库
     *
     * @param questionBankQuestions
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void batchAddQuestionToBankInner(List<QuestionBankQuestion> questionBankQuestions) {
        try {
            boolean result = this.saveBatch(questionBankQuestions);
            ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR, "插入题库题目失败");
        } catch (DataIntegrityViolationException e) {
            log.error("数据库唯一键冲突或违反其他完整性约束 错误信息: {}", e.getMessage());
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "题目已存在于该题库，无法重复添加");
        } catch (DataAccessException e) {
            log.error("数据库连接问题、事务问题等导致操作失败 错误信息: {}", e.getMessage());
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "数据库操作失败");
        } catch (Exception e) {
            // 捕获其他异常，做通用处理
            log.error("添加题目到题库时发生未知错误，题目 错误信息: {}", e.getMessage());
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "向题库添加题目失败");
        }
    }


    /**
     * 批量从题库移除题目
     *
     * @param questionIdList
     * @param questionBankId
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void batchRemoveQuestionToBank(List<Long> questionIdList, long questionBankId) {
        // 参数校验
        ThrowUtils.throwIf(CollUtil.isEmpty(questionIdList), ErrorCode.PARAMS_ERROR, "题目列表不能为空");
        ThrowUtils.throwIf(questionBankId <= 0, ErrorCode.PARAMS_ERROR, "题目 id 非法");

        // 执行删除关联
        for (Long questionId : questionIdList) {
            // 构造查询
            LambdaQueryWrapper<QuestionBankQuestion> queryWrapper = Wrappers.lambdaQuery(QuestionBankQuestion.class)
                    .eq(QuestionBankQuestion::getQuestionId, questionId)
                    .eq(QuestionBankQuestion::getQuestionBankId, questionBankId);
            boolean result = this.remove(queryWrapper);
            ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR, "从题库移除题目失败");
        }
    }
}
